﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace SimpleMapper
{

    /// <summary>
    /// The mapper base method
    /// </summary>
    /// <typeparam name="S"></typeparam>
    /// <typeparam name="T"></typeparam>
    public class Mapper<S, T>
        where S : class
        where T : class
    {

        #region protected properties

        /// <summary>
        /// Gets or Sets Parent child collection
        /// </summary>
        protected List<ParentChild> ParentChildCollection { get; set; }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Retrieve type information for a Generic Type
        /// </summary>
        /// <param name="actualObject">The object </param>
        /// <returns>Type</returns>
        protected Type GetTypeFromGenericObject(object actualObject)
        {
            if (actualObject != null)
            {
                Type typeOfGenericObject = actualObject.GetType().GetGenericArguments()[0];
                return typeOfGenericObject;
            }
            else
            {
                throw new Exception("The generic object is Null");
            }
        }

        /// <summary>
        /// Returns a generic type
        /// </summary>
        /// <param name="objectType">Type</param>
        /// <returns>Type</returns>
        protected Type GetGenericListType(Type objectType)
        {
            try
            {
                var genericType = typeof(System.Collections.Generic.List<>).MakeGenericType(objectType);
                return genericType;
            }
            catch
            {
                throw new Exception("Target type is not a generic type");
            }
        }

        /// <summary>
        /// Invoke method of a object
        /// </summary>
        /// <param name="sourceObject">Object</param>
        /// <param name="methodsName">Method</param>
        /// <returns>Object</returns>
        protected object InvokeMethod(object sourceObject, string methodsName)
        {
            if (sourceObject != null)
            {
                try
                {
                    var result = sourceObject.GetType().GetMethod(methodsName).Invoke(sourceObject, null);
                    return result;
                }
                catch
                {
                    throw new Exception("Could not invoke requested methods");
                }
            }
            else
            {
                throw new Exception("Object is Null");
            }
        }

        /// <summary>
        /// Returns value for a property from an Object
        /// </summary>
        /// <param name="sourceObject">Object</param>
        /// <param name="propertyName">Property Name</param>
        /// <returns>Value as Object</returns>
        protected object GetPropertyValue(object sourceObject, string propertyName)
        {
            if (sourceObject != null)
            {
                try
                {
                    var result = sourceObject.GetType().GetProperty(propertyName).GetValue(sourceObject);
                    return result;
                }
                catch
                {
                    throw new Exception("Could not retrieve requested property");
                }
            }
            else
            {
                throw new Exception("Object is Null");
            }
        }

        /// <summary>
        /// Returns matched property from an object
        /// </summary>
        /// <param name="theObject">Object</param>
        /// <param name="name">Property name</param>
        /// <returns>Property Information</returns>
        protected PropertyInfo GetMatchedProperty(object theObject, string name)
        {
            try
            {
                PropertyInfo matchProperty = theObject.GetType().GetProperty(name);
                return matchProperty;
            }
            catch
            {
                throw new Exception("Error trying matching property");
            }
        }

        /// <summary>
        /// Checks if the Property to be added is also a  parent to the Target property. This is specially for entity object with navigation objects
        /// </summary>
        /// <param name="targetProperty">Target Property</param>
        /// <param name="propertyTobeAdded">String name</param>
        /// <returns>Bool</returns>
        protected bool IsAParent(string targetProperty, string propertyTobeAdded)
        {
            var result = ParentChildCollection.Where(x => x.Parent == propertyTobeAdded && x.Child == targetProperty);
            if (result.Count() > 0)
                return true;
            return false;
        }

        /// <summary>
        /// Sets value to a property of the object
        /// </summary>
        /// <param name="theObject">The Object</param>
        /// <param name="propertyName">The property</param>
        /// <param name="value">the value</param>
        protected void SetValue(object theObject, string propertyName, object value)
        {
            try
            {
                var matchedProperty = GetMatchedProperty(theObject, propertyName);
                if (matchedProperty != null)
                {
                    matchedProperty.SetValue(theObject, value);
                }
            }
            catch
            {
                
            }
        }

        /// <summary>
        /// The inner class that holds the parent child relationship information
        /// </summary>
        protected class ParentChild
        {
            /// <summary>
            /// Parent child class constructor
            /// </summary>
            public ParentChild()
            {
            }

            /// <summary>
            /// Parent child class overloaded constructor
            /// </summary>
            /// <param name="parent">Parent name</param>
            /// <param name="child">Child name</param>
            public ParentChild(string parent, string child)
            {
                this.Parent = parent;
                this.Child = child;
            }
            
            /// <summary>
            /// Gets or Sets the Parent object name
            /// </summary>
            public string Parent { get; set; }
            
            /// <summary>
            /// Gets or sets the child object name
            /// </summary>
            public string Child { get; set; }

        }

        #endregion
    }

}
